// 调用该方法来检测数据
function observe(data) {
	if (typeof data !== 'object') return
	new Observer(data)
}

// 实现一个监听器 Observer：利用 Object.defineproperty 劫持(监听)
// 各个属性的setter 和 getter。数据变动时，就能监听到数据变化。
class Observer {
	constructor(value) {
		this.value = value
		this.walk()
	}
	walk() {
        // 遍历该对象，并进行数据劫持
		Object.keys(this.value).forEach((key) =>
			defineReactive(this.value, key)
		)
	}
}

// 数据拦截
function defineReactive(data, key, value = data[key]) {
	const dep = new Dep()
	observe(value)
	Object.defineProperty(data, key, {
		get: function reactiveGetter() { // 当属性被访问时，会触发 get 函数
			dep.depend()
			return value
		},
		set: function reactiveSetter(newValue) { // 当属性被赋值时，会触发 set 函数
			if (newValue === value) return
			value = newValue
			observe(newValue)
			dep.notify()
		},
	})
}

// 实现一个发布者Dep：采用发布-订阅模式，来收集订阅者 Watcher，
// 对监听器 Observer 和 订 阅者 Watcher 进行统一管理。
// 依赖收集器
class Dep {
	constructor() {
		this.subs = []
	}

	depend() { // 收集依赖
		if (Dep.target) {
			this.addSub(Dep.target)
		}
	}

	notify() {
		const subs = [...this.subs]
		subs.forEach((s) => s.update())
	}

	addSub(sub) {
		this.subs.push(sub)
	}
}

Dep.target = null

const TargetStack = []

function pushTarget(_target) {
	TargetStack.push(Dep.target)
	Dep.target = _target
}

function popTarget() {
	Dep.target = TargetStack.pop()
}

// 实现一个订阅者 Watcher：订阅者是 Observer 和 Compile 之间通信的桥梁 ，负责订阅数据的变化，
// 当数据发生变化时，触发解析器 Compile 中对应的更新函数。
class Watcher {
	// data: 数据对象，如obj
	// expression：表达式，如b.c，根据data和expression就可以获取watcher依赖的数据
	// cb：依赖变化时触发的回调
	constructor(data, expression, cb) {
		this.data = data
		this.expression = expression
		this.cb = cb
		this.value = this.get()
	}

	get() {
		pushTarget(this)
		const value = parsePath(this.data, this.expression)
		popTarget()
		return value
	}

	update() {
		const oldValue = this.value
		this.value = parsePath(this.data, this.expression)
		this.cb.call(this.data, this.value, oldValue)
	}
}

// 工具函数
function parsePath(obj, expression) {
	const segments = expression.split('.')
	for (let key of segments) {
		if (!obj) return
		obj = obj[key]
	}
	return obj
}

// for test
let obj = {
	a: 1,
	b: {
		m: {
			n: 4,
		},
	},
}

observe(obj)

let w1 = new Watcher(obj, 'a', (val, oldVal) => {
	console.log(`obj.a 从 ${oldVal}(oldVal) 变成了 ${val}(newVal)`)
})

console.log(obj)
